﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.IO;

/*
 * source: http://www.codeproject.com/KB/IP/socketasynceventargssampl.aspx
 * 
 * namespace: MrBrooks.Net.TcpClient
 * 
 * comments: processing
 * 
 */

/*
 * 通讯数据格式
 * 
 * [int][Data Length][Data]
 * 
 * 心跳包 [int][0]
 * 
 */

namespace MrBrooks.Processing
{
    /// <summary>
    /// Implements the connection logic for the socket client.
    /// </summary>
    public sealed class TcpClient : IDisposable
    {
        /// <summary>
        /// Constants for socket operations.
        /// </summary>
        private const int ReceiveOperation = 1, SendOperation = 0;

        /// <summary>
        /// 默认的心跳包指令
        /// </summary>
        private const int DEFAULT_KEEP_ALIVE = -6;

        /// <summary>
        /// 默认的心跳包频率
        /// </summary>
        private const int DEFAULT_KEEP_ALIVE_PERIOD = 30;

        /// <summary>
        /// 默认需要心跳包指令
        /// </summary>
        private bool needKeepAlive = true;

        /// <summary>
        /// 心跳包指令
        /// </summary>
        private int keepAliveCommand = 0;

        /// <summary>
        /// 心跳包的发送频率(单位: 秒)
        /// </summary>
        private int keepAlivePeriod = 60;

        /// <summary>
        /// 是否需要自动重新
        /// </summary>
        private bool autoReconnect = true;

        /// <summary>
        /// 重连频率(单位: 秒)
        /// </summary>
        private int reconnectPeriod = 5;

        /// <summary>
        /// The socket used to send/receive messages.
        /// </summary>
        private Socket clientSocket;

        /// <summary>
        /// Flag for connected socket.
        /// </summary>
        private Boolean connected = false;

        /// <summary>
        /// Listener endpoint.
        /// </summary>
        private IPEndPoint hostEndPoint;

        /// <summary>
        /// Signals a connection.
        /// </summary>
        private static AutoResetEvent autoConnectEvent = new AutoResetEvent(false);

        /// <summary>
        /// Signals the send/receive operation.
        /// </summary>
        private static AutoResetEvent[] autoSendReceiveEvents = new AutoResetEvent[]
        {
            new AutoResetEvent(false),
            new AutoResetEvent(false)
        };

        /// <summary>
        /// Create an uninitialized client instance.  
        /// To start the send/receive processing
        /// call the Connect method followed by SendReceive method.
        /// </summary>
        /// <param name="hostName">Name of the host where the listener is running.</param>
        /// <param name="port">Number of the TCP port from the listener.</param>
        public TcpClient(string hostName, int port)
            : this(hostName, port, true, DEFAULT_KEEP_ALIVE, DEFAULT_KEEP_ALIVE_PERIOD)
        {

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="hostName"></param>
        /// <param name="port"></param>
        /// <param name="requireKeepAlive"></param>
        /// <param name="keepAliveCommand"></param>
        /// <param name="keepAlivePeriod"></param>
        public TcpClient(string hostName, int port, bool requireKeepAlive, int keepAliveCommand, int keepAlivePeriod)
        {
            this.needKeepAlive = requireKeepAlive;
            this.keepAlivePeriod = keepAlivePeriod;
            this.keepAliveCommand = keepAliveCommand;

            // Get host related information.
            IPHostEntry host = Dns.GetHostEntry(hostName);

            // Addres of the host.
            IPAddress[] addressList = host.AddressList;

            // Instantiates the endpoint and socket.
            this.hostEndPoint = new IPEndPoint(addressList[addressList.Length - 1], port);
            this.clientSocket = new Socket(this.hostEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        }

        /// <summary>
        /// Connect to the host.
        /// </summary>
        /// <returns>True if connection has succeded, else false.</returns>
        public void Connect()
        {
            SocketAsyncEventArgs connectArgs = new SocketAsyncEventArgs();

            connectArgs.UserToken = this.clientSocket;
            connectArgs.RemoteEndPoint = this.hostEndPoint;
            connectArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnConnect);

            clientSocket.ConnectAsync(connectArgs);
            autoConnectEvent.WaitOne();

            SocketError errorCode = connectArgs.SocketError;
            if (errorCode != SocketError.Success)
            {
                throw new SocketException((int)errorCode);
            }
        }

        /// <summary>
        /// Disconnect from the host.
        /// </summary>
        public void Disconnect()
        {
            clientSocket.Disconnect(false);
        }

        private void OnConnect(object sender, SocketAsyncEventArgs e)
        {
            // Signals the end of connection.
            autoConnectEvent.Set();

            // Set the flag for socket connected.
            var result = (e.SocketError == SocketError.Success);
            if (result)
            {
                this.connected = result;
                Socket socket = e.UserToken as Socket;
                if (ConnectedEvent != null)
                {
                    ConnectedEvent(socket);
                }
            }
        }

        private void OnReceive(object sender, SocketAsyncEventArgs e)
        {
            // Signals the end of receive.
            autoSendReceiveEvents[SendOperation].Set();

            if (e.LastOperation == SocketAsyncOperation.Receive)
            {
                if (e.SocketError == SocketError.Success && e.BytesTransferred > 0)
                {
                    // Prepare receiving.
                    Socket s = e.UserToken as Socket;

                    byte[] receiveBuffer = new byte[255];
                    e.SetBuffer(receiveBuffer, 0, receiveBuffer.Length);
                    e.Completed += new EventHandler<SocketAsyncEventArgs>(OnReceive);
                    s.ReceiveAsync(e);
                }
            }
        }

        private void OnSend(object sender, SocketAsyncEventArgs e)
        {
            // Signals the end of send.
            autoSendReceiveEvents[ReceiveOperation].Set();

            if (e.SocketError == SocketError.Success)
            {
                if (e.LastOperation == SocketAsyncOperation.Send)
                {
                    // Prepare receiving.
                    Socket s = e.UserToken as Socket;

                    byte[] receiveBuffer = new byte[255];
                    e.SetBuffer(receiveBuffer, 0, receiveBuffer.Length);
                    e.Completed += new EventHandler<SocketAsyncEventArgs>(OnReceive);
                    s.ReceiveAsync(e);
                }
            }
            else
            {
                this.ProcessError(e);
            }
        }

        /// <summary>
        /// Close socket in case of failure and throws a SockeException according to the SocketError.
        /// </summary>
        /// <param name="e">SocketAsyncEventArg associated with the failed operation.</param>
        private void ProcessError(SocketAsyncEventArgs e)
        {
            Socket s = e.UserToken as Socket;
            if (s.Connected)
            {
                // close the socket associated with the client
                try
                {
                    s.Shutdown(SocketShutdown.Both);
                }
                catch (Exception)
                {
                    // throws if client process has already closed
                }
                finally
                {
                    if (s.Connected)
                    {
                        s.Close();
                    }
                }
            }

            // Throw the SocketException
            throw new SocketException((int)e.SocketError);
        }

        /// <summary>
        /// 发送数据到服务端
        /// </summary>
        /// <param name="cmd"></param>
        /// <param name="data"></param>
        public void Send(int cmd, Stream data)
        {
            if (this.connected)
            {
                // 最大可传送的数据量为2的31次方..约2G
                int dlength = (int)(data != null ? data.Length : 0);
                int length = dlength + 8;

                // 重新构造数据为 [cmd][data.length][data]
                byte[] bytes = new byte[length];
                BitConverter.GetBytes(cmd).CopyTo(bytes, 0);
                BitConverter.GetBytes(dlength).CopyTo(bytes, 4);
                if (dlength > 0)
                {
                    // 记录旧位置
                    long position = data.Position;
                    data.Position = 0;
                    data.Read(bytes, 8, dlength);
                    data.Position = position;
                }

                // Prepare arguments for send/receive operation.
                SocketAsyncEventArgs completeArgs = new SocketAsyncEventArgs();
                // 写入要发送的数据
                completeArgs.SetBuffer(bytes, 0, length);
                completeArgs.UserToken = this.clientSocket;
                completeArgs.RemoteEndPoint = this.hostEndPoint;
                completeArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSend);

                // Start sending asyncronally.
                clientSocket.SendAsync(completeArgs);

                // Wait for the send/receive completed.
                //AutoResetEvent.WaitAll(autoSendReceiveEvents);
                foreach (var are in autoSendReceiveEvents)
                {
                    are.WaitOne();
                }

                // Return data from SocketAsyncEventArgs buffer.
                //return Encoding.Default.GetString(completeArgs.Buffer, completeArgs.Offset, completeArgs.BytesTransferred);
            }
            else
            {
                throw new SocketException((int)SocketError.NotConnected);
            }
        }

        /// <summary>
        /// 是否为调试模式
        /// </summary>
        public bool DebugMode { get; set; }

        /// <summary>
        /// 警告
        /// </summary>
        /// <param name="title"></param>
        /// <param name="message"></param>
        private void Warning(string title, string message)
        {
            ProcessNotifyEvent(title, message);
        }

        /// <summary>
        /// 消息处理
        /// </summary>
        /// <param name="title"></param>
        /// <param name="message"></param>
        private void ProcessNotifyEvent(string title, string message)
        {
            if (NotifyEvent != null)
            {
                NotifyEvent(string.Format("({0}) {1}", title, message));
            }
        }

        #region .IDisposable 接口实现.
        /// <summary>
        /// Disposes the instance of SocketClient.
        /// </summary>
        public void Dispose()
        {
            autoConnectEvent.Close();
            autoSendReceiveEvents[SendOperation].Close();
            autoSendReceiveEvents[ReceiveOperation].Close();
            if (this.clientSocket.Connected)
            {
                this.clientSocket.Close();
            }
        }
        #endregion

        #region .Events.
        /// <summary>
        /// 连接事件
        /// </summary>
        /// <param name="socket"></param>
        public delegate void ConnectedEventHandler(Socket socket);

        /// <summary>
        /// 已连接事件
        /// </summary>
        public event ConnectedEventHandler ConnectedEvent;

        /// <summary>
        /// 消息事件处理
        /// </summary>
        /// <param name="message"></param>
        public delegate void NotifyEventHandler(string message);

        /// <summary>
        /// 消息事件
        /// </summary>
        public event NotifyEventHandler NotifyEvent;
        #endregion
    }
}
